#include <assert.h>
#include "action.h"
#include "view.h"
#include "region.h"
#include "visoal.h"

static sync_object_t view_action_sync;
static listnode_t action_root = LISTNODE_INITIALIZER(action_root);

// external functions
view_t* view_alloc(void);
void view_free(view_t* v);

rectlist_node_t* rectlist_node_alloc(void);
void rectlist_node_free(rectlist_node_t* nd);

_bool view_can_render(view_t* vi);
view_t* get_top_child_view(view_t* vi);
void view_render_region(listnode_t* head, view_draw_reason_e r);

// this funciton is not locked
static view_action_head_t* view_action_head_alloc(void)
{
	view_action_head_t* head;
	assert(sizeof(view_action_head_t) < sizeof(rectlist_node_t));
	head = (view_action_head_t*)rectlist_node_alloc();
	return head;
}

// this funciton is not locked
static void view_action_head_release(view_action_head_t* head)
{
	if (NULL == head) return;
	rectlist_node_free((rectlist_node_t*)head);
}

// this funciton is not locked
static view_action_node_t* view_action_node_alloc(void)
{
	view_action_node_t* ret;
	assert(sizeof(view_action_node_t) < sizeof(view_t));
	ret = (view_action_node_t*)view_alloc();
	if (ret) listnode_init(ret->ownerlist);
	return ret;
}

// this funciton is not locked
static void view_action_node_release(view_action_node_t* node)
{
	if (NULL == node) return;
	view_free((view_t*)node);
}

// this function is not locked
static void view_action_node_remove_and_release(view_action_node_t* node)
{
	if (NULL == node) return;
	if (!listnode_isempty(node->ownerlist))
		listnode_del(node->ownerlist);
	view_action_node_release(node);
}

// this function is not locked
view_action_head_t* find_action_head_by_view(view_t* vi)
{
	// todo: consider optimization
	// use an avltree to accelerate the search
	listnode_t* item = action_root.next;
	if (NULL == vi) return NULL;

	for (; item != &action_root; item = item->next)
	{
		view_action_head_t* head = list_entry(view_action_head_t, ownerlist, item);
		if (head->vi == vi) return head;
	}
	return NULL;
}

// this function is not locked
static view_action_node_t* find_action_by_actionid(
	view_action_head_t* head, view_actionid_e actionid)
{
	listnode_t* item = head->actionlist.next;
	for (; item != &(head->actionlist); item = item->next)
	{
		view_action_node_t* node = list_entry(view_action_node_t, ownerlist, item);
		if (node->data.actionid == actionid)
			return node;
			
	}
	return NULL;
}

static _bool dup_add_action(view_action_head_t* head, view_action_data_t* actdata)
{
	view_action_node_t* node = view_action_node_alloc();
	if (NULL == node) return false;
	memcpy(&(node->data), actdata, sizeof(view_action_data_t));
	listnode_add(head->actionlist, node->ownerlist);
	return true;
}

// this funciton is not locked
static void do_add_move_resize_action(view_action_head_t* head, view_action_data_t* actdata)
{
	// find if there is an exist move/resize/move_resize object
	view_action_node_t* move_resize_node =
		find_action_by_actionid(head, vi_actionid_move_resize);

	if (NULL == move_resize_node) dup_add_action(head, actdata);
}

// this function is not locked
static void do_add_show_action(view_action_head_t* head, view_action_data_t* actdata)
{
	_bool action_needed = true;
	// when we try to show, first we see if there is an exist show action
	// this seems to be impossible but we do a failsoft
	view_action_node_t* node =
		find_action_by_actionid(head, vi_actionid_show);
	if (node) return;

	// the following action need to be omited
	// (1) previous hide
	// the current show action makes the previous hide action redundant
	// (2) previous move and resize
	// execute this show means the view is in hide before
	// so it is not necessary to draw the move/resize action since the
	// view is hide before
	node = find_action_by_actionid(head, vi_actionid_hide);
	if (node)
	{
		view_action_node_remove_and_release(node);
		action_needed = false;
	}
	node = find_action_by_actionid(head, vi_actionid_move_resize);
	if (node) view_action_node_remove_and_release(node);

	// add the new hide action
	if (action_needed) dup_add_action(head, actdata);
}

// this function is not locked
static void do_add_hide_action(view_action_head_t* head, view_action_data_t* actdata)
{
	_bool action_needed = true;
	// when we try to hide, first we see if there is an exist hide action
	// this seems to be impossible but we do a failsoft
	view_action_node_t* node =
		find_action_by_actionid(head, vi_actionid_hide);
	if (node) return;

	// the following action need to be omitted
	// (1) previous show
	// the current hide action makes the previous show action redundant
	// (2) previous move and resize
	// it is not necessary to draw the move/resize action since the
	// view is hidden
	// (3) todo
	node = find_action_by_actionid(head, vi_actionid_show);
	if (node)
	{
		view_action_node_remove_and_release(node);
		action_needed = false;
	}
	node = find_action_by_actionid(head, vi_actionid_move_resize);
	if (node) view_action_node_remove_and_release(node);

	// add the new hide action
  	if (action_needed) dup_add_action(head, actdata);

	// notify
	view_notify_event(head->vi, view_ev_show_event, NULL);
}

// this function is not locked
static void do_add_action(view_action_head_t* head, view_action_data_t* actdata)
{
	switch (actdata->actionid)
	{
		case vi_actionid_move_resize:
			do_add_move_resize_action(head, actdata);
			break;

		case vi_actionid_show:
			do_add_show_action(head, actdata);
			break;

		case vi_actionid_hide:
			do_add_hide_action(head, actdata);
			break;

		default: break;
	}
}

static view_action_head_t* create_action_head(view_t* vi)
{
	view_action_head_t* head = view_action_head_alloc();
	if (NULL == head) return NULL;
	listnode_init(head->actionlist);
	listnode_add(action_root, head->ownerlist);
	head->vi = vi;
	return head;
}

int view_add_action(view_t* vi, view_action_data_t* actdata)
{
	view_action_head_t *head;

	if (NULL == vi || NULL == actdata)
		return 1;

	// lock
	sync_start(&view_action_sync);

	head = find_action_head_by_view(vi);
	if (NULL == head)
	{
		head = create_action_head(vi);
		if (NULL == head)
		{
			sync_end(&view_action_sync);
			return 2;
		}
	}

	do_add_action(head, actdata);
	sync_end(&view_action_sync);
	return 0;
}

// this function is not locked
static void view_handle_show_action(view_t* vi, view_action_data_t* actdata)
{
	region_t* rgn;
	listnode_t head = LISTNODE_INITIALIZER(head);

	if (!view_can_render(vi))
		return;

	rgn = region_create();
	if (NULL == rgn) return;
	region_add_view_area(vi, rgn);
	invalidate(get_top_child_view(vi), rgn, &head);
	view_render_region(&head, vw_draw_reason_show);
	region_free(rgn);
}

// this function is not locked
static void view_execute_single_action(view_t* vi, view_action_node_t* actnode)
{
	switch (actnode->data.actionid)
	{
		case vi_actionid_move_resize:
			break;

		case vi_actionid_show:
			view_handle_show_action(vi, &actnode->data);
			break;

		case vi_actionid_hide:
			break;

		default: break;
	}
}

// this function is not locked
static void view_do_execute_action(view_action_head_t* head)
{
	while (!listnode_isempty(head->actionlist))
	{
		listnode_t* item = head->actionlist.next;
		view_action_node_t* node = list_entry(view_action_node_t, ownerlist, item);
		listnode_del(node->ownerlist);
		view_execute_single_action(head->vi, node);
		view_action_node_release(node);
	}
}

void view_execute_action(void)
{
	// need lock
	sync_start(&view_action_sync);

	// handle each view
	while (!listnode_isempty(action_root))
	{
		listnode_t* item = action_root.next;
		view_action_head_t* head = list_entry(view_action_head_t, ownerlist, item);
		listnode_del(head->ownerlist);
		view_do_execute_action(head);
		view_action_head_release(head);
	}
	sync_end(&view_action_sync);
}

void global_init_view_action(void)
{
	prepare_sync_object(view_action_sync);
}

void global_destroy_view_action(void)
{
	release_sync_object(view_action_sync);
}

/* EOF */
